﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using QUT.Bio.SilverMap.MapService;
using QUT.Bio.SilverMap.ObjectModel;
using QUT.Bio.Util;
using System.Linq;

namespace QUT.Bio.SilverMap.DefaultProvider {
	/// <summary>
	/// The object model associated with the default data provider.
	/// <para>This model provisions itself from a web service hosted (originally) at QUT.</para>
	/// </summary>
	
	public class ObjectModel : IFeatureCollection {

		internal readonly Dictionary<string, Chromosome> chromosomes = new Dictionary<string, Chromosome>();
		internal readonly Dictionary<decimal, ILinearDomain> genes = new Dictionary<decimal, ILinearDomain>();
		internal readonly HashList<IFeature> features = new HashList<IFeature>();
		internal readonly List<Hit> hits = new List<Hit>();
		internal readonly HashList<ILinearDomain> querySequences = new HashList<ILinearDomain>();
		internal readonly DataLayer dataLayer;
		private ILinearDomain referenceGene;
		private DistanceType distanceType = DistanceType.EValue;
		private double cutoff = 0.5;
		private int maxHits = 100;
		private readonly HashList<Chromosome> selectedChromosomes = new HashList<Chromosome>();

		#region Event: ChromosomesLoaded
		/// <summary>
		/// Event that fires when the list of available chromosomes has been loaded.
		/// </summary>

		internal event Action ChromosomesLoaded; 
		#endregion

		#region Event: LoadComplete
		/// <summary>
		///	Event that fires when load has successfully completed. 
		/// </summary>

		public event Action<IFeatureCollection> LoadComplete; 
		#endregion

		#region Event: ReferenceSequenceUpdated
		/// <summary>
		/// Event that fires when the reference sequence changes.
		/// </summary>
		
		public event Action<IFeatureCollection> ReferenceSequenceUpdated;
		#endregion

		#region Constructor
		/// <summary>
		/// Initialises this ObjectModel, which in turn encapsulates a web service connection 
		/// that talks to a database.
		/// </summary>
		/// <param name="overlay">A popup overlay that can be used to display dialogs and alerts in a portable manner.</param>

		public ObjectModel ( PopupOverlay overlay ) {
			dataLayer = new DataLayer( this, overlay );
			LoadChromosomes();
		} 
		#endregion

		#region Property: Domains
		/// <summary>
		/// Gets a list of sequences presently loaded.
		/// </summary>

		public IEnumerable<ILinearDomain> Domains {
			get {
				return genes.Values;
			}
		} 
		#endregion

		#region Property: Features.
		/// <summary>
		/// Gets a list of genomic features.
		/// </summary>

		public IEnumerable<IFeature> Features {
			get {
				return features;
			}
		} 
		#endregion

		#region Property: Hits.
		/// <summary>
		/// Gets a list of hits, which relate two features.
		/// </summary>

		public IEnumerable<Hit> Hits {
			get {
				return hits;
			}
		} 
		#endregion

		/// <summary>
		/// 
		/// </summary>
		
		internal Dictionary<string, Chromosome> Chromosomes {
			get {
				return chromosomes;
			}
		}

		#region Property: SelectedChromosomes
		/// <summary>
		/// Gets a list of chromosomes from which hits will be loaded.
		/// This is set as a side effect of Load.
		/// </summary>
		
		internal IEnumerable<Chromosome> SelectedChromosomes {
			get {
				return selectedChromosomes;
			}
		}
		
		#endregion

		#region Property: DistanceType;
		/// <summary>
		/// Gets the current distance type. This is set as a side effect of
		/// </summary>

		internal DistanceType DistanceType {
			get {
				return distanceType;
			}
		} 
		#endregion

		#region Property: Cutoff
		/// <summary>
		/// Get or set the search threshold value.
		/// </summary>

		internal double Cutoff {
			get {
				return cutoff;
			}
			set {
				if ( value <= 0 ) {
					throw new ArgumentException( "Cut-off must be greater than 0." );
				}

				cutoff = value;
			}
		}
		#endregion
		
		#region Property: MaxHits
		public int MaxHits {
			get {
				return maxHits;
			}
			set {
				if ( value <= 0 ) {
					throw new ArgumentException( "MaxHits must be greater than 0." );
				}

				maxHits = value;
			}
		}
		#endregion

		#region Property: TagsEnabled
		/// <summary>
		/// Enable or disable the tag search capability in the loader.
		/// </summary>

		public bool TagsEnabled {
			get;
			set;
		} 
		#endregion

		/// <summary>
		/// Get or set the reference sequence.
		/// <para>
		///		Mutator side-effect: fires the ReferenceSequenceUpdated event if the new reference sequence
		///		is not the same as the old one.
		/// </para>
		/// </summary>
		
		internal ILinearDomain ReferenceSequence {
			get {
				return referenceGene;
			}
			set {
				if ( value == null && referenceGene != null
				|| referenceGene == null && value != null
				|| !value.Equals( referenceGene )
			) {
					referenceGene = value;

					if ( ReferenceSequenceUpdated != null ) {
						ReferenceSequenceUpdated( this );
					}
				}
			}
		}

		internal readonly List<Chromosome> Subjects = new List<Chromosome>();

		#region Method: LoadChromosomes
		/// <summary>
		/// Loads the known sequences from the web service, and when completed triggers the ChromosomesLoaded event.
		/// </summary>

		private void LoadChromosomes () {
			dataLayer.ListAvailableChromosomes( delegate( Chromosome[] c ) {
				chromosomes.Clear();

				foreach ( Chromosome chr in c ) {
					chromosomes.Add( chr.Accession, chr );
				}

				if ( ChromosomesLoaded != null ) {
					ChromosomesLoaded();
				}
			} );
		} 
		#endregion

		#region Method: Load
		/// <summary>
		/// Loads a new sequence collection from the web service encapsulated by the data layer.
		/// </summary>
		/// <param name="cutoff">The similarity threshold.</param>
		/// <param name="distanceType">The distance type: e-value, positives, identities</param>
		/// <param name="selectedChromosomes">A list of subject organisms.</param>
		/// <param name="referenceGene">The query sequence.</param>
		/// <param name="maxHits">The maximum number of hits to load.</param>

		internal void Load (
			IEnumerable<Chromosome> selectedChromosomes,
			Gene referenceGene,
			DistanceType distanceType,
			double cutoff,
			int maxHits
		) {
			List<string> accessions = new List<string>();

			foreach ( Chromosome c in selectedChromosomes ) {
				accessions.Add( c.Accession );
			}

			if ( accessions.Count == 0 ) {
				return;
			}

			genes.Clear();
			hits.Clear();
			querySequences.Clear();
			
			this.selectedChromosomes.Clear();
			this.selectedChromosomes.AddRange( selectedChromosomes );
			this.distanceType = distanceType;
			this.Cutoff = cutoff;
			this.MaxHits = maxHits;
			ReferenceSequence = referenceGene;

			dataLayer.GetBlastResults(
				referenceGene.Id,
				DistanceType,
				Cutoff,
				accessions.ToArray(),
				maxHits,
				delegate( Gene gene ) {
					genes.Add( gene.Id, gene );
				},
				delegate( Hit hit ) {
					hits.Add( hit );
					if ( !querySequences.Contains( hit.QueryFeature.ContainingSequence ) ) {
						querySequences.Add( hit.QueryFeature.ContainingSequence );
					}
				},
				delegate() {
					if ( LoadComplete != null ) {
						LoadComplete( this );
					}
				}
			);
		} 
		#endregion

		#region Method: LoadReferenceGene
		/// <summary>
		/// Loads basic node information about the selected reference node.
		/// </summary>

		public void LoadReferenceGene ( decimal referenceGeneId ) {
			bool loadOk = false;

			dataLayer.GetBasicGeneInfo(
				new decimal[] { referenceGeneId },
				delegate( Gene gene ) {
					ReferenceSequence = gene;
					loadOk = true;
				},
				delegate() {
					if ( !loadOk ) {
						Alert.Show( "Reference Gene {0} not found!", referenceGeneId );
					}
				}
			);
		} 
		#endregion

		#region Property: InitialFocus
		/// <summary>
		/// Gets the initial query sequence, which is used to set up the initial view on load from file.
		/// </summary>

		public ILinearDomain InitialFocus {
			get {
				return ReferenceSequence;
			}
		} 
		#endregion

		#region Property: Focii
		/// <summary>
		/// Gets a list of query sequqnces currently loaded.
		/// </summary>

		public HashList<ILinearDomain> Focii {
			get {
				return querySequences;
			}
		} 
		#endregion
	}
}
